#include "U2EGLSupport.h"

#include "U2Exception.h"
#include "U2LogManager.h"
#include "U2StringConverter.h"
#include "U2GLESRenderSystem.h"





U2EG_NAMESPACE_USING


U2EGLSupport::U2EGLSupport()
: mGLDisplay(0)
, mRandr(false)
{
//        mGLDisplay = getGLDisplay();
//		mNativeDisplay = getNativeDisplay();

}

U2EGLSupport::~U2EGLSupport()
{
}

void U2EGLSupport::addConfig(void)
{
    ConfigOption optFullScreen;
    ConfigOption optVideoMode;
    ConfigOption optDisplayFrequency;
    ConfigOption optFSAA;
    ConfigOption optRTTMode;

    optFullScreen.name = "Full Screen";
    optFullScreen.immutable = false;

    optVideoMode.name = "Video Mode";
    optVideoMode.immutable = false;

    optDisplayFrequency.name = "Display Frequency";
    optDisplayFrequency.immutable = false;

    optFSAA.name = "FSAA";
    optFSAA.immutable = false;

    optRTTMode.name = "RTT Preferred Mode";
    optRTTMode.immutable = false;

    optFullScreen.possibleValues.push_back("No");
    optFullScreen.possibleValues.push_back("Yes");

    optFullScreen.currentValue = optFullScreen.possibleValues[1];

    VideoModes::const_iterator value = mVideoModes.begin();
    VideoModes::const_iterator end = mVideoModes.end();

    for (; value != end; value++)
    {
        U2String mode = U2StringConverter::toString(value->first.first,4) + " x " + U2StringConverter::toString(value->first.second,4);
        optVideoMode.possibleValues.push_back(mode);
    }
    removeDuplicates(optVideoMode.possibleValues);

    optVideoMode.currentValue = U2StringConverter::toString(mCurrentMode.first.first,4) + " x " + U2StringConverter::toString(mCurrentMode.first.second,4);

    refreshConfig();
    if (!mSampleLevels.empty())
    {
        U2StringVector::const_iterator value = mSampleLevels.begin();
        U2StringVector::const_iterator end = mSampleLevels.end();

        for (; value != end; value++)
        {
            optFSAA.possibleValues.push_back(*value);
        }

        optFSAA.currentValue = optFSAA.possibleValues[0];
    }

    optRTTMode.possibleValues.push_back("Copy");
    optRTTMode.currentValue = optRTTMode.possibleValues[0];

    mOptions[optFullScreen.name] = optFullScreen;
    mOptions[optVideoMode.name] = optVideoMode;
    mOptions[optDisplayFrequency.name] = optDisplayFrequency;
    mOptions[optFSAA.name] = optFSAA;
    mOptions[optRTTMode.name] = optRTTMode;

    refreshConfig();
}

void U2EGLSupport::refreshConfig(void) 
{
    U2ConfigOptionMap::iterator optVideoMode = mOptions.find("Video Mode");
    U2ConfigOptionMap::iterator optDisplayFrequency = mOptions.find("Display Frequency");

    if (optVideoMode != mOptions.end() && optDisplayFrequency != mOptions.end())
    {
        // first we clear the out-of-date frequency
        optDisplayFrequency->second.possibleValues.clear();

        VideoModes::const_iterator value = mVideoModes.begin();
        VideoModes::const_iterator end = mVideoModes.end();

        for (; value != end; value++)
        {
            U2String mode = U2StringConverter::toString(value->first.first,4) + " x " + U2StringConverter::toString(value->first.second,4);
            // if an alternative screen size equal to current screen size, 
            // then add relevant frequency to the optDisplayFrequency
            if (mode == optVideoMode->second.currentValue)
            {
                U2String frequency = U2StringConverter::toString(value->second) + " Hz";

                optDisplayFrequency->second.possibleValues.push_back(frequency);
            }
        }

        if (!optDisplayFrequency->second.possibleValues.empty())
        {
            optDisplayFrequency->second.currentValue = optDisplayFrequency->second.possibleValues[0];
        }
        else
        {
            optVideoMode->second.currentValue = U2StringConverter::toString(mVideoModes[0].first.first,4) + " x " + U2StringConverter::toString(mVideoModes[0].first.second,4);
            optDisplayFrequency->second.currentValue = U2StringConverter::toString(mVideoModes[0].second) + " Hz";
        }
    }
}

void U2EGLSupport::setConfigOption(const U2String &name, const U2String &value)
{
    U2GLESSupport::setConfigOption(name, value);
    if (name == "Video Mode")
    {
        refreshConfig();
    }
}

U2String U2EGLSupport::validateConfig(void)
{
    // TODO
    return U2StringUtil::BLANK;
}

//Moved to native.
//	NativeDisplayType EGLSupport::getNativeDisplay()
//	{
//		return EGL_DEFAULT_DISPLAY; // TODO
//	}

EGLDisplay U2EGLSupport::getGLDisplay(void)
{
    EGLint major, minor;

	mGLDisplay = eglGetDisplay(mNativeDisplay);

    if(mGLDisplay == EGL_NO_DISPLAY)
    {
        U2_EXCEPT(U2Exception::ERR_RENDERINGAPI_ERROR,
                    "Couldn`t open EGLDisplay " + getDisplayName(),
                    "EGLSupport::getGLDisplay");
    }

    if (eglInitialize(mGLDisplay, &major, &minor) == EGL_FALSE)
    {
        U2_EXCEPT(U2Exception::ERR_RENDERINGAPI_ERROR,
                    "Couldn`t initialize EGLDisplay ",
                    "EGLSupport::getGLDisplay");
    }
	return mGLDisplay;
}


U2String U2EGLSupport::getDisplayName(void)
{
	return "todo";
}

EGLConfig* U2EGLSupport::chooseGLConfig(const EGLint *attribList, EGLint *nElements)
{
    EGLConfig *configs;

    if (eglChooseConfig(mGLDisplay, attribList, NULL, 0, nElements) == EGL_FALSE)
    {
        assert(false);
        U2_EXCEPT(U2Exception::ERR_RENDERINGAPI_ERROR,
                    "Fail to choose config",
                    __FUNCTION__);

        *nElements = 0;
        return 0;
	}

    configs = (EGLConfig*) malloc(*nElements * sizeof(EGLConfig));
    if (eglChooseConfig(mGLDisplay, attribList, configs, *nElements, nElements) == EGL_FALSE)
    {
        U2_EXCEPT(U2Exception::ERR_RENDERINGAPI_ERROR,
                    "Fail to chosse config",
                    __FUNCTION__);

        *nElements = 0;
        free(configs);
		return 0;
    }

    return configs;
}

EGLBoolean U2EGLSupport::getGLConfigAttrib(EGLConfig glConfig, EGLint attribute, EGLint *value)
{
    EGLBoolean status;

    status = eglGetConfigAttrib(mGLDisplay, glConfig, attribute, value);

    return status;
}

void* U2EGLSupport::getProcAddress(const U2String& name)
{
    return (void*)eglGetProcAddress((const char*) name.c_str());
}

::EGLConfig U2EGLSupport::getGLConfigFromContext(::EGLContext context)
{
    ::EGLConfig glConfig = 0;

    if (eglQueryContext(mGLDisplay, context, EGL_CONFIG_ID, (EGLint *) &glConfig) == EGL_FALSE)
    {
        U2_EXCEPT(U2Exception::ERR_RENDERINGAPI_ERROR,
                    "Fail to get config from context",
                    __FUNCTION__);
        return 0;
    }

    return glConfig;
}

::EGLConfig U2EGLSupport::getGLConfigFromDrawable(::EGLSurface drawable,
                                                unsigned int *w, unsigned int *h)
{
    ::EGLConfig glConfig = 0;

    if (eglQuerySurface(mGLDisplay, drawable, EGL_CONFIG_ID, (EGLint *) &glConfig) == EGL_FALSE)
    {
        U2_EXCEPT(U2Exception::ERR_RENDERINGAPI_ERROR,
                    "Fail to get config from drawable",
                    __FUNCTION__);
        return 0;
    }

    eglQuerySurface(mGLDisplay, drawable, EGL_WIDTH, (EGLint *) w);
    eglQuerySurface(mGLDisplay, drawable, EGL_HEIGHT, (EGLint *) h);

    return glConfig;
}



//------------------------------------------------------------------------
// A helper class for the implementation of selectFBConfig
//------------------------------------------------------------------------
class GLConfigAttribs
{
public:
    GLConfigAttribs(const EGLint* attribs)
    {
        fields[EGL_CONFIG_CAVEAT] = EGL_NONE;

        for (int i = 0; attribs[2*i] != EGL_NONE; i++)
        {
            fields[attribs[2*i]] = attribs[2*i+1];
        }
    }

    void load(U2EGLSupport* const glSupport, EGLConfig glConfig)
    {
        std::map<EGLint,EGLint>::iterator it;

        for (it = fields.begin(); it != fields.end(); it++)
        {
            it->second = EGL_NONE;

            glSupport->getGLConfigAttrib(glConfig, it->first, &it->second);
        }
    }

    bool operator>(GLConfigAttribs& alternative)
    {
        // Caveats are best avoided, but might be needed for anti-aliasing
        if (fields[EGL_CONFIG_CAVEAT] != alternative.fields[EGL_CONFIG_CAVEAT])
        {
            if (fields[EGL_CONFIG_CAVEAT] == EGL_SLOW_CONFIG)
            {
                return false;
            }

            if (fields.find(EGL_SAMPLES) != fields.end() &&
                fields[EGL_SAMPLES] < alternative.fields[EGL_SAMPLES])
            {
                return false;
            }
        }

        std::map<EGLint,EGLint>::iterator it;

        for (it = fields.begin(); it != fields.end(); it++)
        {
            if (it->first != EGL_CONFIG_CAVEAT &&
                fields[it->first] > alternative.fields[it->first])
            {
                return true;
            }
        }

        return false;
    }

    std::map<EGLint,EGLint> fields;
};



::EGLConfig U2EGLSupport::selectGLConfig(const EGLint* minAttribs, const EGLint *maxAttribs)
{
    EGLConfig *glConfigs;
    EGLConfig glConfig = 0;
    EGLint config, nConfigs = 0;

    glConfigs = chooseGLConfig(minAttribs, &nConfigs);

    if (!nConfigs)
    {
        return 0;
    }

    glConfig = glConfigs[0];

    if (maxAttribs)
    {
        GLConfigAttribs maximum(maxAttribs);
        GLConfigAttribs best(maxAttribs);
        GLConfigAttribs candidate(maxAttribs);

        best.load(this, glConfig);

        for (config = 1; config < nConfigs; config++)
        {
            candidate.load(this, glConfigs[config]);

            if (candidate > maximum)
            {
                continue;
            }

            if (candidate > best)
            {
                glConfig = glConfigs[config];

                best.load(this, glConfig);
            }
        }
    }

    free(glConfigs);
    return glConfig;
}

void U2EGLSupport::switchMode(void)
{
    return switchMode(mOriginalMode.first.first,
                      mOriginalMode.first.second, mOriginalMode.second);
}

U2RenderWindow* U2EGLSupport::createWindow(bool autoCreateWindow,
                                       GLESRenderSystem* renderSystem,
                                       const U2String& windowTitle)
{
    U2RenderWindow *window = 0;

    if (autoCreateWindow)
    {
        U2ConfigOptionMap::iterator opt;
        U2ConfigOptionMap::iterator end = mOptions.end();
        NameValuePairList miscParams;

        bool fullscreen = false;
        u2uint w = 640, h = 480;

        if ((opt = mOptions.find("Full Screen")) != end)
        {
            fullscreen = (opt->second.currentValue == "Yes");
        }

        if ((opt = mOptions.find("Display Frequency")) != end)
        {
            miscParams["displayFrequency"] = opt->second.currentValue;
        }

        if ((opt = mOptions.find("Video Mode")) != end)
        {
            U2String val = opt->second.currentValue;
            U2String::size_type pos = val.find('x');

            if (pos != U2String::npos)
            {
                w = U2StringConverter::parseUnsignedInt(val.substr(0, pos));
                h = U2StringConverter::parseUnsignedInt(val.substr(pos + 1));
            }
        }

        if ((opt = mOptions.find("FSAA")) != end)
        {
            miscParams["FSAA"] = opt->second.currentValue;
        }

        window = renderSystem->_createRenderWindow(windowTitle, w, h, fullscreen, &miscParams);
    }

    return window;
}

//    RenderWindow* EGLSupport::newWindow(const U2String &name,
//                                        unsigned int width, unsigned int height,
//                                        bool fullScreen,
//                                        const NameValuePairList *miscParams)
//    {
//        EGLWindow* window = createEGLWindow(this);
//
//        window->create(name, width, height, fullScreen, miscParams);
//
//        return window;
//    }

::EGLContext U2EGLSupport::createNewContext(EGLDisplay eglDisplay,
										  ::EGLConfig glconfig,
                                          ::EGLContext shareList) const 
{
	::EGLContext context = ((::EGLContext) 0);
	if (eglDisplay == ((EGLDisplay) 0))
	{
		context = eglCreateContext(mGLDisplay, glconfig, shareList, 0);
	}
	else
	{
		context = eglCreateContext(eglDisplay, glconfig, 0, 0);
	}

	if (context == ((::EGLContext) 0))
    {
        U2_EXCEPT(U2Exception::ERR_RENDERINGAPI_ERROR,
                    "Fail to create New context",
                    __FUNCTION__);
        return 0;
    }

    return context;
}

void U2EGLSupport::start()
{
}

void U2EGLSupport::stop()
{
}

void U2EGLSupport::setGLDisplay( EGLDisplay val )
{
	mGLDisplay = val;
}
